/*
 * Matt Adams
 * TCSS 558 HW2
 * Fall 2013
 */
package server;

import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Iterator;
import java.util.SortedSet;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

import tools.Log;

/**
 * Allows multiple TCP clients to access a single TCP spelling server.
 * 
 * @author Matt Adams
 */
public class ConcurrentTCPSpellingServer extends SpellingServer {
    
    /**
     * Default maximum number of sessions allowed.
     */
    private static final int DEFAULT_MAX_SESSIONS = 5;
    
    /**
     * Server socket used.
     */
    private final ServerSocket serverSocket;
    
    /**
     * Handles queuing and executing threads.
     */
    private final ThreadPoolExecutor threadPoolExecutor;
    
    /**
     * Number of initialized sessions.
     */
    private int numInitializedSessions;
    
    /**
     * Constructs ConcurrentTCPSpellingServer object.
     * 
     * @param portFilenameArray Listing of port and filename.
     * @param maxNumSessions Maximum number of sessions.
     */
    public ConcurrentTCPSpellingServer(String[] portFilenameArray, final int maxNumSessions) {
        super(portFilenameArray);
        this.numInitializedSessions = 0;
        this.serverSocket = createServerSocket();
        this.threadPoolExecutor = new ThreadPoolExecutor(maxNumSessions, maxNumSessions, 10L, TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>());
        Log.out("Created concurrent TCP spelling server with max number of sessions as: " + maxNumSessions);
    }
    
    /**
     * Initializes server and waits to accept a connection.
     */
    public void initializeServer() {
        try {
            Socket clientSocket = this.serverSocket.accept();
            this.numInitializedSessions++;
            Log.out("    Session " + this.numInitializedSessions + ": Accepted connection from <" + 
                    clientSocket.getInetAddress().getHostAddress() + ">:<" + clientSocket.getPort() + ">");
            
            this.threadPoolExecutor.execute(new ConcurrentTCPServerRunnable(clientSocket, this, this.numInitializedSessions));
        } catch (IOException e) {
            Log.err("Failed to setup server for receiving data: " + e);
            errorClose();
        }
    }
    
    /**
     * Sets up socket.
     */
    private ServerSocket createServerSocket() {
        ServerSocket serverSocket = null;
        try {            
            serverSocket = new ServerSocket(this.port);
        } catch (IOException e) {
            Log.err("Error creating socket for ConcurrentTCP spelling server: " + e.getMessage());
            errorClose();
        }
        return serverSocket;
    }
    
    /**
     * Handles receiving data from a client and sending a response back to the same client.
     */
    @Override
    protected void receiveAndSend() throws IOException {
        initializeServer();      
    }
    
    /**
     * Properly displays server response to query.
     * 
     * @param queriedWord Queried word.
     * @param numberOfWords Number of close words.
     * @param closeWords Set of close words.
     * @return Returns formatted string of close words.
     */
    public String displayQueryResponse(final String queriedWord, 
                                       final int numberOfWords, 
                                       final SortedSet<String> closeWords, 
                                       final int sessionNumber) {
        String closeWordString = null;
        if (numberOfWords < 0) {
            Log.out(sessionNumber + " - " + queriedWord + " is spelled correctly.");
        } else if (numberOfWords == 0) {
            Log.out(sessionNumber + " - " + queriedWord + " is spelled incorrectly. There are no suggestions.");
        } else {     
            final StringBuilder sb = new StringBuilder();
            final Iterator<String> itr = closeWords.iterator();
            int count = 1;
            while (itr.hasNext() && count <= numberOfWords) {
                final String closeWord = itr.next();
                sb.append(closeWord);
                sb.append(" ");
                count++;
            }
            closeWordString = sb.toString();
            Log.out(sessionNumber + " - " + queriedWord + " is spelled incorrectly.");
            Log.out(sessionNumber + " - There are " + numberOfWords + " suggested words: " + closeWordString);
        }
        return closeWordString;
    }
    
    /**
     * Main to run server.
     * @param portFilenameArray Listing of port and filename.
     */
    public static void main(String[] portFileSessionsArray) {
        if (portFileSessionsArray.length < 2) {
            Log.err("Usage: java UDPSpellingServer <port> <word-list> <number of concurrent sessions>");           
        } else {
            int maxNumSessions = 0;
            if (portFileSessionsArray.length > 2) {
                try {
                    maxNumSessions = Integer.parseInt(portFileSessionsArray[2]);
                } catch (NumberFormatException e) {
                    Log.err("Not valid integer value for number of concurrent sessions: " + e);
                    Log.out("Setting number of concurrent sessions to default value: " + DEFAULT_MAX_SESSIONS);
                    maxNumSessions = DEFAULT_MAX_SESSIONS;
                }
            } else {
                Log.out("Setting number of concurrent sessions to default value: " + DEFAULT_MAX_SESSIONS);
                maxNumSessions = DEFAULT_MAX_SESSIONS;
            }
            final String[] portFilenameArray = new String[2];
            portFilenameArray[0] = portFileSessionsArray[0];
            portFilenameArray[1] = portFileSessionsArray[1];
            
            final ConcurrentTCPSpellingServer tcpServer = new ConcurrentTCPSpellingServer(portFilenameArray, maxNumSessions);
            tcpServer.runServer();
        }
    }

}
